var Utils = require('geoUtils');

var ArrayUtils = {
    /**
     * 从from到to的整数构造数组
     * @param {*} from 
     * @param {*} to 
     */
    buildNumberArray(from, to) {
        var ret = [];
        for (var n = from; n <= to; ++n)
            ret.push(n);
        return ret;
    },
    /**
     * 构建一个带有指定模式的序列。
     * 给定一组元素itemsFrom，给定一个模式pattern，返回由itemsFrom中抽取的元素
     * 组成的符合pattern的数组，并可选择性从itemsFrom中剔除被抽取的元素。
     * @param {array} itemsFrom 可选用的元素数组。
     * @param {array} pattern 返回的数组要遵从的模式。
     * @param {int} sequenceLength 指定返回的序列长度，若长于pattern，则会重复pattern。
     * @param {boolean} affectFrom 是否从itemsFrom中剔除选中的元素。
     */
    buildSequence(itemsFrom, pattern, sequenceLength, affectFrom){
        // 假设itemsFrom=['A', 'B', 'C']，pattern = ['x', 'z', 'x', 'z', 'z']，sequenceLength = 7。
        // >1< 获取模式中的元素种类
        // patternUnits = {
        //     'x': 2,
        //     'z': 3
        // }
        var patternUnits = Utils.array.countMatch([pattern]);
        // >2< 得到模式元素名称数组
        // patternUnitNames = ['x', 'z'];
        var patternUnitNames = Object.getOwnPropertyNames(patternUnits);
        // >3< 从itemsFrom中抽取与patternUnitNames对应数量的元素
        // itemsPicked = ['A', 'B'] or ['A', 'C'] or ['B', 'C']
        var numItemsPicked = patternUnitNames.length;
        var itemsPicked = ArrayUtils.random(itemsFrom, numItemsPicked, affectFrom);
        // >4< 将itemsPicked(备选元素)与patternUnitNames(模式中的元素)做关联
        // relations = {
        //     'x': 'A',
        //     'z': 'B'
        // } or {
        //     'x': 'A',
        //     'z': 'C'
        // } or {
        //     'x': 'B',
        //     'z': 'C'
        var relations = [];
        for (var i = 0; i < numItemsPicked; ++i){
            var patternUnitName = patternUnitNames[i];
            var item = itemsPicked[i];
            relations[patternUnitName] = item;
        }
        // >5< 构建序列
        // ret = ['A', 'B', 'A', 'B', 'B', 'A', 'B']
        var ret = [];
        for (var i = 0; i < sequenceLength; ++i){
            // 获得模式中的元素
            var patternItem = pattern[i % pattern.length];
            // 获得模式元素对应的备选元素
            var item = relations[patternItem];
            // 将备选元素放入序列
            ret.push(item);
        }
        //
        return ret;
    },
    /**
     * 从一个数组中随机取出指定数量的元素
     * 如果指定数量与数组长度一致，则相当于对数组进行乱序
     * @param {Array} arrayFrom 元素所在数组
     * @param {int} count 要取出的元素数量
     * @param {bool} affectFrom 是否从原数组中剔除提取的元素
     */
    random(arrayFrom, count, affectFrom) {
        var tmp = arrayFrom;
        //
        if (affectFrom !== true)
            tmp = arrayFrom.concat();
        //
        var length = arrayFrom.length;
        if (count <= 0)
            count = length;
        else
            count = Math.min(count, length);
        var ret = [];
        for (var i = 0; i < count; ++i) {
            // 随机获取下标
            var n = Math.floor(Math.random() * tmp.length);
            // 去除并移除这个元素
            var item = tmp.splice(n, 1);
            // 将item加入返回数组
            ret.push(item[0]);
        }
        return ret;
    },
    /**
     * 返回数组中匹配到的元素的索引
     * @param {Array} array 
     * @param {*} item 
     * @param {bool} useStrict 是否采取严格模式匹配，即===，否则采取==模式。
     * @return {int} -1 表示没有匹配项。
     */
    indexOf(array, item, useStrict) {
        if (useStrict == true) {
            for (var i = 0; i < array.length; ++i) {
                if (array[i] === item)
                    return i;
            }
        } else {
            for (var i = 0; i < array.length; ++i) {
                if (array[i] == item)
                    return i;
            }
        }
        //
        return -1;
    },
    /**
     * 
     * @param {array} array 
     * @param {any} item 
     * @param {bool} useStrict 是否采取严格模式匹配，即===，否则采取==模式。
     * @return {bool} true，成功删除，false，没找到该元素
     */
    remove(array, item, useStrict) {
        var i = ArrayUtils.indexOf(array, item, useStrict);
        if (i < 0)
            return false;
        //
        array.splice(i, 1);
        return true;
    },
    /**
     * 为arraysToMatch中的数组中的元素进行匹配测试，
     * 返回各个元素的匹配次数
     * @param {Array2D} arraysToMatch 二位数组，各个数组中的元素会用于相互匹配
     * @param {[int]} countFilter 只有规定计数的元素会被返回
     * @param {Array} excepts 不参与匹配的元素（值）
     * @param {bool} useStrict 是否采取严格模式匹配，即===，否则采取==模式。
     * @return {Object} 各个元素的匹配次数，比如
     * ['x','s'],['s','abc'],['s']匹配的话，将得到如下结果
     * {
     *      'x': 1,
     *      's': 3,
     *      'abc': 1
     * }
     * 若传入['abc']作为excepts，则得到如下结果
     * {
     *      'x': 1,
     *      's': 3
     * }
     */
    countMatch(arraysToMatch, countFilter, excepts, useStrict) {
        countFilter = countFilter || [];
        excepts = excepts || [];
        var ret = {};
        // 收集元素及计数
        arraysToMatch.forEach(e => {
            e.forEach(f => {
                if (ArrayUtils.indexOf(excepts, f, useStrict) === -1) {
                    var t = ret[f];
                    if (t === void 0)
                        ret[f] = 1;
                    else
                        ret[f]++;
                }
            });
        });
        // 过滤计数
        if (countFilter.length > 0) {
            for (var i in ret) {
                if (ArrayUtils.indexOf(countFilter, ret[i], useStrict) === -1)
                    delete ret[i];
            }
        }
        return ret;
    },
    /**
     * validateFeatures的更名版本，推荐使用此版本。
     * @param {*} items 数组类型
     * @param {*} reference 设置items应该参考的特征，空则仅考虑items的共同特征。
     * @param {*} featureFunc items中的item获得特征值的方法，方法原型为：
     * featureFunc(item){
     *      var itemFeatures = [];
     *      // 获取item特征的算法
     *      ...
     *      // 返回item的特征数组，如['大', '红', '圆']
     *      return itemFeatures; 
     * }
     * @returns 匹配结果，结构如下：
     * {
     *      featuresMatched:[],
     *      featuresUnmatched:[],
     *      itemsMatched:[],
     *      itemsUnmatched:[],
     * }
     */
    featureMatch(items, reference, featureFunc) {
        var ret = Utils.logic.validateFeatures(items, Utils.logic.Mode.ANY, reference, featureFunc);
        //
        return {
            featuresMatched: ret.commonFeatures,
            featuresUnmatched: ret.differentFeatures,
            itemsMatched: ret.validItems,
            itemsUnmatched: ret.invalidItems,
        };
    },
    /**
     * 匹配两个序列是否一致。
     * 参考：以下序列都是相同的。
     * [1,2,1,2,3,1,2]
     * [2,1,2,1,6,2,1]
     * [0,'a', 0, 'a', 1, 0, 'a']
     * [nodeA, nodeC, nodeA, nodeC, nodeB, nodeA, nodeC]
     * @param {*} sequenceA 序列A
     * @param {*} sequenceB 序列B
     * @param {*} getMatchValueFuncA 用于获取序列A中元素匹配值的方法对象，若不指定则使用元素本身作为匹配值。
     * @param {*} getMatchValueFuncB 用于获取序列B中元素匹配值的方法对象，若不指定则使用元素本身作为匹配值。
     * @example
     * 以
     */
    sequenceMatch(sequenceA, sequenceB, getMatchValueFuncA, getMatchValueFuncB){
        //
        var defalutMatchValueFunc = (item)=>{
            return item;
        };
        //
        getMatchValueFuncA || (getMatchValueFuncA = defalutMatchValueFunc);
        getMatchValueFuncB || (getMatchValueFuncB = defalutMatchValueFunc);
        //
        var a = Utils.logic.getArrayPattern(sequenceA, getMatchValueFuncA);
        var b = Utils.logic.getArrayPattern(sequenceB, getMatchValueFuncB);
        //
        return a.pattern == b.pattern;
    },
    /**
     * 寻找arraysToMatch中每个数组都共有的元素
     * @param {Array2D} arraysToMatch 
     * @param {Array} excepts 不考虑的元素（值）
     * @param {bool} useStrict 是否采取严格模式匹配，即===，否则采取==模式。
     * @returns {Array} 共有的元素，如果没有共有元素，则数组长度为0。
     * @example
     * 设arraysToMatch为
     * [
     *   ['大', '红', '方', '新'], 
     *   ['大', '蓝', '方', '新'], 
     *   ['大', '粉', '方', '新']
     * ]
     * > 设excepts为[]或不指定
     * 则返回结果为
     * ['大', '方', '新']
     * > 若设excepts为['方', '圆', '新', '旧']
     * 则返回结果为
     * ['大']
     * 以下为可用的测试代码：
    var arraysToMatch =
        [
            ['大', '红', '方', '新'],
            ['大', '蓝', '方', '新'],
            ['大', '粉', '方', '新']
        ];
    var excepts = ['方', '圆', '新', '旧'];

    cc.log("1:" + ArrayUtils.commonItems(arraysToMatch));
    cc.log("2:" + ArrayUtils.commonItems(arraysToMatch, excepts));

    var res = ArrayUtils.commonItems(arraysToMatch);
    cc.log("3:" + res);
    excepts = res.slice(0, 1);
    cc.log("4:" + excepts);
    var res = ArrayUtils.commonItems(arraysToMatch, excepts);
    cc.log("5:" + res);
     */
    commonItems(arraysToMatch, excepts, useStrick) {
        // 获取数组数量
        var numArrays = arraysToMatch.length;
        // 获得计数匹配结果
        var countMatchResult = ArrayUtils.countMatch(arraysToMatch, [numArrays], excepts, useStrick);
        // 收集结果
        var ret = [];
        for (var attr in countMatchResult) {
            ret.push(attr);
        }
        //
        return ret;
    },
    /**
     * 从指定元素结合中挑选指定数量的元素完成组合计算
     * @param {Array} array 元素集合
     * @param {int} numItemsPerCombination 
     */
    combination1D(array, numItemsPerCombination) {
        if (array.length < numItemsPerCombination)
            return [];
        //
        var combines = [];
        ArrayUtils._combineRecursive(combines, [], array, numItemsPerCombination);
        return combines;
    },
    /**
     * 从指定元素集合中挑选指定数量的元素完成排列计算
     * @param {Array} array 
     * @param {int} numItemsPerPermutation 
     */
    permutation1D(array, numItemsPerPermutation) {
        if (array.length < numItemsPerPermutation)
            return [];
        //
        var permutation = [];
        ArrayUtils._combineRecursive(permutation, [], array, numItemsPerPermutation, true);
        return permutation;
    },
    /**
     * 内部方法，不建议直接使用
     * @param {Array} combines 用于接收最终结果
     * @param {*} branchResult 在目前分支上收集的结果
     * @param {Array} items 带分配的元素
     * @param {int} level 层级，为0则返回
     * @param {bool} usePermutation 是否为排列
     */
    _combineRecursive(combines, branchResult, items, level, usePermutation) {
        if (level == 0) {
            // 收集结果
            combines.push(branchResult);
            return;
        }
        --level;
        for (var i = 0; i < items.length; ++i) {
            // 选定当前元素
            var item = items[i];
            // 将当前元素和剩余元素传递下去[a][bcd]/[b][cd]
            ArrayUtils._combineRecursive(combines, branchResult.concat([item]),
                usePermutation === true ? items : items.slice(i + 1),
                level, usePermutation);
        }
    },
    /**
     * 发现src中元素排列的模式
     * @param {[*]} sample 
     * @param {boolean} useStrict 是否启用===。
     * @returns {[[]]}模式数组，数组中每个元素也是数组
     * @example
     * 
    var sample = [1,2,1,2,1,2,1,2,1,2,1,2,1];
    var ret = ArrayUtils.findPatterns(sample);
     * 以上代码ret最终为
     [
         [1, 2],
         [1, 2, 1, 2],
         [1, 2, 1, 2, 1, 2]
     ]
     */
    findPatternsInSequence(sample, useStrict) {
        var ret = [];
        var start = 0, end = 1;
        var len = sample.length;
        var halfLen = Math.floor(len / 2);
        while (end <= halfLen) {
            // 本次模式
            var pattern = sample.slice(start, end);
            // 达标匹配数量
            var okCount = Math.floor(len / pattern.length);
            // 获得匹配数量
            var count = ArrayUtils.countPatternInSequence(pattern, sample, useStrict);
            // 如果达标就保存
            if (count >= okCount)
                ret.push(pattern);
            // 准备下一轮
            ++end;
        }
        //
        return ret;
    },
    /**
     * 返回A在B中依次出现的次数
     * @param {[*]} A 
     * @param {[*]} B 
     * @param {boolean} useStrict 是否启用===。
     * @returns {number} 返回出现的次数
     */
    countPatternInSequence(A, B, useStrict) {
        var round = 0;
        var len = A.length;
        while (true) {
            // 将A中元素依次与B中对应位置元素匹配
            var startB = round * len;
            var m = ArrayUtils.matchItemsInSequence(A, 0, B, startB, len, useStrict);
            //
            if (m.length != len)
                break;
            //
            ++round;
        }
        //
        return round;
    },
    /**
     * A中从startA位置开始，向B中从startB位置开始，依次匹配，
     * 返回匹配的元素
     * @param {[*]} A 
     * @param {number} startA 
     * @param {[*]} B 
     * @param {number} startB 
     * @param {number} len 
     * @param {boolean} useStrict 是否启用===。
     * @returns {[*]} 返回匹配的元素
     */
    matchItemsInSequence(A, startA, B, startB, len, useStrict) {
        var limitA = startA + len;
        var limitB = startB + len;
        var ret = [];
        for (var iA = startA, iB = startB; iA < limitA && iB < limitB; ++iA, ++iB) {
            var a = A[iA];
            var b = B[iB];
            if ((useStrict === true && a === b) || (a == b))
                ret.push(a);
            else
                break;
        }
        //
        return ret;
    },
};

module.exports = ArrayUtils;